using System;
using System.Diagnostics;
using System.IO;
using Microsoft.WowAddonStudio.Lua.RuntimeService.ShellHost;
using Microsoft.WowAddonStudio.Lua.RuntimeService.WowBench;

namespace Microsoft.WowAddonStudio.Lua.RuntimeService.ShellHost
{
	/// <summary>
	/// Implementation of IShellHost for Windows Command Shell (cmd.exe) or other console application.
	/// </summary>
	/// <remarks>
	/// To host cmd.exe or lua.exe, an actual cmd.exe process is started where its output, input and error streams are redirected to stream properties
	/// required by IShellHost. To issue commands to shell, that command is directly written in to input stream. 
	/// 
	/// When we keep a running copy of cmd.exe, we gain the capability to allow environment variables that user set during shell interaction
	/// to be persistent. But we are not allowed to access those environment variables using Process object, thus ExpandEnvironmentVariables
	/// method is not implemented for cmd.exe host.
	/// </remarks>
	[Serializable]
	public sealed class CommandShellHost : BaseShellHost
	{
		/// <summary>
		/// Executes command in shell.
		/// </summary>
		/// <param name="command">The command string.</param>
		public override void ExecuteCommand(string command)
		{
			lastCommand = command;
			//We need to convert the input string into the proper encoding for cmd.exe (ibm850)
			var streamWriter = new StreamWriter(InputStream.BaseStream, consoleEncoding);
			streamWriter.WriteLine(command);
			streamWriter.Flush();
		}

		/// <summary>
		/// This method is called when ShellTextBox creates the host instance for the first time. 
		/// </summary>
		/// <param name="startingDirectory">Preferred directory to start in.</param>
		/// <param name="outputWriter">The <see cref="IOutputWriter"/> parameter.</param>
		/// <param name="executableName">The name of executable to load.</param>
		/// <remarks>This method will only be called once at the beginning.</remarks>
		public override bool LaunchShellProcess(IOutputWriter outputWriter, string startingDirectory, string executableName)
		{
			return LaunchShellProcess(outputWriter, startingDirectory, executableName, string.Empty);
		}

		/// <summary>
		/// This method is called when ShellTextBox creates the host instance for the first time. 
		/// </summary>
		/// <param name="startingDirectory">Preferred directory to start in.</param>
		/// <param name="outputWriter">The <see cref="IOutputWriter"/> parameter.</param>
		/// <param name="executableName">The name of executable to load.</param>
		/// <param name="argument">The argument for executable.</param>
		/// <remarks>This method will only be called once at the beginning.</remarks>
		public override bool LaunchShellProcess(IOutputWriter outputWriter, string startingDirectory, string executableName, string argument)
		{
			lock (syncObject)
			{
				shellName = executableName;
				arguments = argument;
				writer = outputWriter;

				// Create process, turning off the window and redirecting I/O to us
				var processStartInfo = new ProcessStartInfo(shellName);

				processStartInfo.Arguments = arguments;
				processStartInfo.CreateNoWindow = true;
				processStartInfo.RedirectStandardError = true;
				processStartInfo.RedirectStandardInput = true;
				processStartInfo.RedirectStandardOutput = true;
				processStartInfo.UseShellExecute = false;
				processStartInfo.StandardOutputEncoding = consoleEncoding;
				processStartInfo.StandardErrorEncoding = consoleEncoding;

				shellProcess = Process.Start(processStartInfo);
				bool result = shellProcess != null;

				if (result)
				{
					SetRedirectedStreamsUnBuffered();
					ShellState = ShellHostState.Running;
					processId = shellProcess.Id;
					Trace.WriteLine(String.Format("Running process with Id [{0}]", processId));
					shellProcess.EnableRaisingEvents = true;
					InputStream.AutoFlush = true;
					shellProcess.Exited += ProcessExited;

					BeginErrorRead();
					BeginOutputRead();
				}
				return result;
			}
		}

		/// <summary>
		/// This method is called when shell has exited and it should be restarted
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		private void ProcessExited(object sender, EventArgs e)
		{
			Trace.WriteLine(String.Format("Process Exited...[{0}]", shellProcess.ExitCode));

			ShellState = ShellHostState.Closed;
			RaiseOnProcessExited();
			lock (syncObject)
			{
				shellProcess.Close();
			}
		}

		#region Methods - Output, Error Stream Handling

		/// <summary>
		/// Begins the error read.
		/// </summary>
		private void BeginErrorRead()
		{
			shellProcess.StandardError.
				BaseStream.BeginRead(errorBuffer, 0, errorBuffer.Length, OnErrorOutputReceivedCallback, shellProcess);
		}


		/// <summary>
		/// Begins the output read.
		/// </summary>
		private void BeginOutputRead()
		{
			shellProcess.StandardOutput.
				BaseStream.BeginRead(outputBuffer, 0, outputBuffer.Length, OnOutputReceivedCallback, shellProcess);
		}


		/// <summary>
		/// Called when [error output received callback].
		/// </summary>
		/// <param name="result">The result.</param>
		private void OnErrorOutputReceivedCallback(IAsyncResult result)
		{
			lock (syncObject)
			{
				if (result.AsyncState != shellProcess) return;
				int byteCount = shellProcess.StandardError.BaseStream.EndRead(result);

				if (byteCount > 0)
				{
					string text = shellProcess.StandardError.
						CurrentEncoding.GetString(errorBuffer, 0, byteCount);

					Debug.WriteLine("OnErrorOutputReceivedCallback:" + text);

					writer.WriteErrorText(text);

					BeginErrorRead();
				}
			}
		}

		/// <summary>
		/// Called when [output received callback].
		/// </summary>
		/// <param name="result">The result.</param>
		private void OnOutputReceivedCallback(IAsyncResult result)
		{
			lock (syncObject)
			{
				if (result.AsyncState != shellProcess) return;

				int  byteCount = shellProcess.StandardOutput.BaseStream.EndRead(result);

				if ( byteCount > 0)
				{
					string text = shellProcess.StandardOutput.
						CurrentEncoding.GetString(outputBuffer, 0,  byteCount);

					Debug.WriteLine("OnOutputReceivedCallback:" + text);

					if (lastCommand != null)
						lastCommand += "\r\n";

					if (!text.Equals(lastCommand))
					{
						writer.WriteFormat(text);
						lastCommand = null;
					}

					BeginOutputRead();
				}
			}
		}

		#endregion
	}
}